package com.thinkgem.jeesite.common.utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * Created by liufeng11 on 2016/10/24.
 */
public class PackageScanUtil {


    private final static Log log = LogFactory.getLog(PackageScanUtil.class);

    //扫描  scanPackages 下的文件的匹配符
    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";


    /**
     * 利用spring 类扫描方式 根据路径获取包下所有class
     *
     * @param scanPackages
     * @return
     */
    public static Set<Class<?>> findPackageClass(String scanPackages) {

        if (StringUtils.isBlank(scanPackages)) {
            return Collections.EMPTY_SET;
        }
        //验证及排重包路径,避免父子路径多次扫描
        Set<String> packages = checkPackages(scanPackages);

        Set<Class<?>> clazzSet = new HashSet<Class<?>>();

        //Spring提供了ResourcePatternResolver接口来加载多个Resource
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

        //spring中用来读取resource为class的工具
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

        for (String basePackage : packages) {
            if (StringUtils.isBlank(basePackage)) {
                continue;
            }
            String packageScanPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN;
            try {
                Resource[] resources = resourcePatternResolver.getResources(packageScanPath);
                for (Resource resource : resources) {
                    if (resource.isReadable()) {
                        clazzSet.add(Class.forName(loadClassName(metadataReaderFactory, resource)));
                    }
                }

            } catch (Exception e) {
                log.error("获取包下面的类信息失败,package:" + scanPackages, e);
            }
        }


        return clazzSet;
    }


    /**
     * 加载资源，根据resource获取className
     *
     * @param metadataReaderFactory spring中用来读取resource为class的工具
     * @param resource              这里的资源就是一个Class
     * @throws Exception
     */
    private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource) throws IOException {
        try {
            if (resource.isReadable()) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                if (metadataReader != null) {
                    return metadataReader.getClassMetadata().getClassName();
                }
            }
        } catch (Exception e) {
            log.error("根据resource获取类名称失败", e);
        }
        return null;
    }

    /**
     * 排重、检测package父子关系，避免多次扫描
     *
     * @param scanPackages
     * @return 返回检查后有效的路径集合
     */
    private static Set<String> checkPackages(String scanPackages) {

        if (StringUtils.isBlank(scanPackages)) {
            return Collections.EMPTY_SET;
        }

        Set<String> packages = new HashSet<String>();
        Collections.addAll(packages, StringUtils.split(scanPackages, ","));
        for (String aPackage : packages.toArray(new String[packages.size()])) {

            if (StringUtils.isBlank(aPackage) || aPackage.equals(".") || aPackage.startsWith(".")) {
                continue;
            }
            if (aPackage.endsWith(".")) {
                aPackage = aPackage.substring(0, aPackage.length() - 1);
            }
            Iterator<String> packageIte = packages.iterator();
            boolean needAdd = true;
            while (packageIte.hasNext()) {
                String pack = packageIte.next();
                if (aPackage.startsWith(pack + ".")) {
                    //如果待加入的路径是已经加入的pack的子集，不加入
                    needAdd = false;
                } else if (pack.startsWith(aPackage + ".")) {
                    //如果待加入的路径是已经加入的pack的父集，删除已加入的pack
                    packageIte.remove();
                }
            }
            if (needAdd) {
                packages.add(aPackage);
            }
        }
        return packages;

    }


    public static void main(String[] args) {
        String packages = "com.a,com.ab,com.c,com.as.t,com.as,com.as.ta,com.at.ja,com.at.jc,com.at.";
        System.out.println("检测前的package: " + packages);
        System.out.println("检测后的package: " + StringUtils.join(checkPackages(packages), ","));
    }


}
